package main

import (
	"fmt"
	"github.com/pkg/errors"
	"runtime"
	"sync"
	"time"
)

type MyMap struct {
	mp    map[string]int
	mutex *sync.Mutex
}

// 多线程学习
func main() {
	// testGosched()
	// testGoExit()
	// testChan()
	// testChanSort()
	// testMulitChan()
	// testTimeout()
	// testSync()
	// message()
}

func message() {
	messages := make(chan string)
	go func() { messages <- "ping" }() // 使用"<-"向通道发送消息
	msg := <-messages                  // 从通道读取数据
	fmt.Println(msg)
}

// 线程同步
// 一个线程写文件，一个线程读文件。
func testSync() {
	m := &MyMap{mp: make(map[string]int), mutex: new(sync.Mutex)}
	go SetValue(m)
	go m.Display()
	var str string
	fmt.Scan(&str)
}

func (this *MyMap) Get(key string) (int, error) {
	this.mutex.Lock()
	i, ok := this.mp[key]
	this.mutex.Unlock()
	if !ok {
		return i, errors.New("不存在")
	}
	return i, nil
}

func (this *MyMap) Set(key string, val int) {
	this.mutex.Lock()
	defer this.mutex.Unlock()
	this.mp[key] = val
}

func (this *MyMap) Display() {
	this.mutex.Lock()
	defer this.mutex.Unlock()
	for key, val := range this.mp {
		fmt.Println(key, "=", val)
	}
	fmt.Println("Display:", this.mp)
}

func SetValue(m *MyMap) {
	var a rune
	a = 'a'
	for i := 0; i < 10; i++ {
		m.Set(string(a+rune(i)), i)
		fmt.Println("SetValue:", i)
	}
}

// 当一个channel被read/write阻塞时，会被一直阻塞下去，直到channel关闭。产生一个异常退出程序。channel内部没有超时的定时器。但我们可以用select来实现channel的超时机制
func testTimeout() {
	c := make(chan int)
	select {
	case <-c:
		fmt.Println("没有数据")
	case <-time.After(5 * time.Second):
		fmt.Println("超时退出")
	}
}

// 多个 chan
func testMulitChan() {
	c := make(chan int)
	quit := make(chan int)
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Printf("%d", <-c)
		}
		quit <- 1
	}()
	muti(c, quit)
}

func muti(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			x, y = y, x+y
			fmt.Printf("\nx = %d, y = %d", x, y)
		case <-quit:
			fmt.Println("\nquit")
			return
		}
	}
}

// 发送和接收的顺序调试
func testChanSort() {
	c := make(chan int, 2)
	c <- 1
	c <- 2
	fmt.Println(<-c)
	fmt.Println(<-c)
}

// 之间通过channel来通讯，可以认为channel是一个管道或者先进先出的队列。你可以从一个goroutine中向channel发送数据，在另一个goroutine中取出这个值
// 生产者/消费者是最经典的使用示例。生产者goroutine负责将数据放入channel，消费者goroutine从channel中取出数据进行处理。
// 使用make创建
func testChan() {
	buf := make(chan int)
	flg := make(chan int)
	go producer(buf)
	go consumer(buf, flg)
	<-flg // 等待接受完成
}

func producer(c chan<- int) {
	defer close(c) // 关闭chan
	for i := 0; i < 10; i++ {
		c <- i // 阻塞, 直到数据被消费者取走之后, 才能发送下一条数据
	}
}

// <-chan int 仅能接收
// chan<-int 仅能发送
func consumer(c <-chan int, f chan<- int) {
	for {
		if v, ok := <-c; ok {
			fmt.Print(v) // 阻塞, 直到生产者放入数据后继续读取数据
		} else {
			break
		}
	}
	f <- 1 // 发送数据, 通知main函数已接受完成
}

func testGoExit() {
	go goexit()
	var str string
	fmt.Scan(&str) // 待输入的意思，在这里用来阻止主线程关闭的
}

// runtime.Goexit() 函数用于终止当前的goroutine，单defer函数将会继续被调用。
func goexit() {
	defer func() {
		fmt.Println("in defer")
	}()
	for i := 0; i < 10; i++ {
		fmt.Print(i)
		if i > 5 {
			runtime.Goexit()
		}
	}
}

func testGosched() {
	go sayHello()
	go sayWorld()
	var str string
	fmt.Scan(&str) // 待输入的意思，在这里用来阻止主线程关闭的
}

// runtime.Gosched() 让当前正在执行的goroutine放弃CPU执行权限。调度器安排其他正在等待的线程运行。
func sayHello() {
	for i := 0; i < 10; i++ {
		fmt.Print("hello")
		runtime.Gosched()
	}
}

func sayWorld() {
	for i := 0; i < 10; i++ {
		fmt.Println("world")
		runtime.Gosched()
	}
}
